use std::{ptr, sync::OnceLock};

use crate::{JEnv, JInt};

#[repr(transparent)]
#[derive(Debug, Clone, Copy)]
pub struct JVm {
	pub(crate) inner: *mut sys::JavaVM,
}

unsafe impl Send for JVm {}
unsafe impl Sync for JVm {}

impl JVm {
	pub const DEFAULT_VERSION: JInt = sys::JNI_VERSION_1_6;
	pub fn as_sys(&self) -> *mut sys::JavaVM {
		self.inner
	}
	pub fn from_sys(vm: *mut sys::JavaVM) -> Option<Self> {
		if !vm.is_null() {
			Some(Self { inner: vm })
		} else {
			None
		}
	}
	pub fn from_void(vm: *mut std::ffi::c_void) -> Option<Self> {
		Self::from_sys(vm as *mut sys::JavaVM)
	}
	fn vtable(&self) -> &sys::JNIInvokeInterface_ {
		unsafe { &*(*self.inner) }
	}
	pub fn singleton(v: Option<JVm>) -> Option<JVm> {
		static CACHE: OnceLock<JVm> = OnceLock::new();
		if let Some(jvm) = CACHE.get() {
			Some(jvm.clone())
		} else {
			if let Some(v) = v {
				CACHE.set(JVm { inner: v.inner }).ok();
			}
			CACHE.get().cloned()
		}
	}
	pub fn attach(&self) -> Option<&'static JEnv> {
		let mut ptr = ptr::null_mut();
		if let Some(fun) = self.vtable().AttachCurrentThread {
			if sys::JNI_OK == unsafe { fun(self.inner, &mut ptr, ptr::null_mut()) } {
				return JEnv::from_sys(ptr);
			}
		}
		None
	}
	pub fn attach_as_daemon(&self) -> Option<&'static JEnv> {
		let mut ptr = ptr::null_mut();
		if let Some(fun) = self.vtable().AttachCurrentThreadAsDaemon {
			if sys::JNI_OK == unsafe { fun(self.inner, &mut ptr, ptr::null_mut()) } {
				return JEnv::from_sys(ptr);
			}
		}
		None
	}
	pub fn detach(&self) {
		if let Some(fun) = self.vtable().DetachCurrentThread {
			let _ret = unsafe { fun(self.inner) };
		}
	}
	pub fn env_with_version(&self, version: sys::jint) -> Option<&'static JEnv> {
		let mut ptr = ptr::null_mut();

		if let Some(fun) = self.vtable().GetEnv {
			if sys::JNI_OK == unsafe { fun(self.inner, &mut ptr, version) } {
				return JEnv::from_sys(ptr);
			}
		}
		None
	}
	pub fn env(&self) -> Option<&'static JEnv> {
		self.env_with_version(Self::DEFAULT_VERSION)
	}
	pub fn env_1_6(&self) -> Option<&'static JEnv> {
		self.env_with_version(sys::JNI_VERSION_1_6)
	}
	pub fn env_1_4(&self) -> Option<&'static JEnv> {
		self.env_with_version(sys::JNI_VERSION_1_4)
	}
	pub fn env_1_2(&self) -> Option<&'static JEnv> {
		self.env_with_version(sys::JNI_VERSION_1_2)
	}
	pub fn env_1_1(&self) -> Option<&'static JEnv> {
		self.env_with_version(sys::JNI_VERSION_1_1)
	}
}

#[test]
fn test_type_len() {
	assert_eq!(std::mem::size_of::<JVm>(), std::mem::size_of::<*mut sys::JavaVM>());
}
